home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Modules / BackSpaceModules / Source / Fortune / FortuneView.m < prev    next >
Encoding:
Text File  |  1994-04-06  |  14.8 KB  |  722 lines

  1. #import "FortuneView.h"
  2. #import <math.h>
  3. #import <ctype.h>
  4. #import "Thinker.h"
  5.  
  6. // this code is copyright Darcy Brockbank, 1994
  7. //
  8. // You may freely reuse and distribute this code in any way shape or
  9. // form, provided that this notice stays intact.
  10. //
  11. // darcy@hasc.ca, samurai@cs.mcgill.ca
  12. //
  13. // If you do improve this thing, send me a copy!
  14. //
  15. // - darcy
  16.  
  17. #define INCLUDED (char)0
  18. #define EXCLUDED (char)1
  19.  
  20. #define SHADOW 0
  21. #define HIGHLIGHT 1
  22. #define PLAIN 2
  23.  
  24. #define SPEED        "FortuneSpeed"
  25. #define USECOLOR    "FortuneUseColor"
  26. #define F_FILE        "FortuneFile"
  27. #define BACKING        "FortuneBacking"
  28. #define CLEARAFTER    "FortuneClearAfter"
  29. #define EXFONTS        "FortuneExcludedFonts"
  30.  
  31. #define ROT(c) ((isupper(c))? ('A' + (c - 'A' + 13) % 26) : \
  32.             ((islower(c)) ? ('a' + (c - 'a' + 13) % 26) : c))
  33.  
  34. @interface ThreeField : TextField
  35. {
  36.     int drawBacking;
  37. }
  38.  
  39. - setDrawBacking:(int)flag;
  40. - drawSelf:(const NXRect *)r :(int)c;
  41.  
  42. @end
  43.  
  44. @implementation ThreeField
  45.  
  46. - setDrawBacking:(int)flag;
  47. {
  48.     drawBacking = flag;
  49.     return self;
  50. }
  51.  
  52.  
  53. - drawSelf:(const NXRect *)r :(int)c;
  54. {
  55.     NXColor realColor = [self textColor];
  56.     NXRect hoffset=*r;
  57.     if (strlen([self stringValue])==0){
  58.         NXSetColor(NX_COLORBLACK);
  59.         NXRectFill(r);
  60.     } else if (drawBacking==HIGHLIGHT){
  61.         hoffset.origin.x+=1.0;
  62.         hoffset.origin.y+=1.0;
  63.         [cell setTextColor:NX_COLORWHITE];
  64.         [cell drawInside:&hoffset inView:self];
  65.         [cell setTextColor:realColor];
  66.         [cell drawInside:r inView:self];
  67.     } else {
  68.         [cell drawInside:r inView:self];
  69.     }
  70.     return self;
  71. }
  72.  
  73. @end
  74.  
  75. @interface GrayCell : NXBrowserCell
  76. {
  77.     BOOL isGray;
  78. }
  79. - setGrayed:(BOOL)flag;
  80. @end
  81.  
  82. @implementation GrayCell
  83. - setGrayed:(BOOL)flag;
  84. {
  85.     isGray = flag;
  86.     return self;
  87. }
  88.  
  89. - setTextAttributes:sender;
  90. {
  91.     [super setTextAttributes:sender];
  92.     if (isGray) [sender setTextGray:NX_DKGRAY];
  93.     return self;
  94. }
  95.  
  96. @end
  97.  
  98. @implementation FortuneView
  99.  
  100. #define MINX 0.0
  101. #define MINY 0.0
  102. #define SIZE 36.0
  103.  
  104. static const char * const fortunes[] = {
  105. #define NORMAL 0
  106.     "fortunes",
  107. #define STARTREK 1
  108.     "startrek",
  109. #define OFFENSIVE 2
  110.     "fortunes-o",
  111. #define ZIPPY 3
  112.     "zippy",
  113.     0
  114. };
  115.  
  116.  
  117. - newDatabase:sender;
  118. {
  119.     int sel = [[sender selectedCell] tag];
  120.     if (sel == OFFENSIVE) {
  121.         if (NXRunAlertPanel("Be sure...", "This option will print fortune entries on the screen "
  122.                 "which some people will find offensive (hence the name). If you "
  123.                 "are offended by offcolor jokes, or sexually explicit remarks, "
  124.                 "or are easily offended at all, then I suggest you not use this option. "
  125.                 "The fact that these fortunes are included as part of this screen "
  126.                 "saver does not imply that I condone or agree with any of them. "
  127.                 "They are included since they were part of the Berkeley 'fortune' package.",
  128.                 "Continue","Cancel Selection",0)==NX_ALERTALTERNATE)
  129.         {
  130.             [offensiveMatrix perform:@selector(selectCellWithTag:)
  131.                     with:(id)fortuneFile
  132.                     afterDelay:1
  133.                     cancelPrevious:YES];
  134.             return self;
  135.         }
  136.     }
  137.     fortuneFile = sel;
  138.     [self loadWords:fortunes[fortuneFile]];
  139.     [self perform:@selector(writeFileDefault:)
  140.         with:self
  141.         afterDelay:1000
  142.         cancelPrevious:YES];
  143.     [self lockFocus];
  144.     [self doDrawing];
  145.     [self unlockFocus];
  146.     return self;
  147. }
  148.  
  149.  
  150.  
  151. - writeThresholdDefault:sender
  152. {
  153.     char tmp[128];
  154.     sprintf(tmp,"%u",threshold);
  155.     NXWriteDefault([NXApp appName],SPEED,tmp);
  156.     return self;
  157. }
  158. - writeColorDefault:sender
  159. {
  160.     char tmp[128];
  161.     sprintf(tmp,"%s",(useColor)?"YES":"NO");
  162.     NXWriteDefault([NXApp appName],USECOLOR,tmp);
  163.     return self;
  164. }
  165.  
  166. - writeFileDefault:sender
  167. {
  168.     char tmp[128];
  169.     sprintf(tmp,"%d",fortuneFile);
  170.     NXWriteDefault([NXApp appName],F_FILE,tmp);
  171.     return self;
  172. }
  173.  
  174.  
  175. - writeBackingDefault:sender
  176. {
  177.     char tmp[128];
  178.     sprintf(tmp,"%d",gray);
  179.     NXWriteDefault([NXApp appName],BACKING,tmp);
  180.     return self;
  181. }
  182.  
  183.  
  184. - setThreshold:sender
  185. {
  186.     threshold = [sender intValue];
  187.     [self perform:@selector(writeThresholdDefault:)
  188.         with:self
  189.         afterDelay:1000
  190.         cancelPrevious:YES];
  191.     return self;
  192. }
  193.  
  194. - setSpeedSlider:sender;
  195. {
  196.     speedSlider = sender;
  197.     [sender setIntValue:threshold];
  198.     return self;
  199. }
  200.  
  201.  
  202. - loadFonts;
  203. {
  204.     fontNames = [[FontManager new] availableFonts];
  205.     if (fontNames) {
  206.         for(numFonts = 0;fontNames[numFonts];numFonts++);
  207.     }
  208.     return self;
  209. }
  210.  
  211.  
  212. - loadWords:(const char *)database;
  213. {
  214.     char buf[MAXPATHLEN];
  215.     
  216.     if (fortuneStream) {
  217.         NXCloseMemory(fortuneStream,NX_FREEBUFFER);
  218.     } 
  219.     sprintf(buf,"%s/db/%s",[appowner moduleDirectory:"Fortune"],database);
  220.     fortuneStream = NXMapFile(buf,NX_READONLY);
  221.     if (fortuneStream) {
  222.         NXGetMemoryBuffer(fortuneStream,&words,&len,&maxlen);
  223.     } else {
  224.         NXRunAlertPanel("File not found.","I couldn't locate the fortune database "
  225.                 "'%s'",NULL,NULL,"OK",buf);
  226.     }
  227.     return self;
  228. }
  229.  
  230. - (BOOL)isExcluded:(const char *)fontName
  231. {
  232.     int i,count = numFonts;
  233.     for(i=0;i<count;i++){
  234.         if (strcmp(fontName,fontNames[i])==0) {
  235.             if (excluded[i]==EXCLUDED) {
  236.                 return YES;
  237.             } else {
  238.                 break;
  239.             }
  240.         }
  241.     }
  242.     return NO;
  243. }
  244.  
  245. - prepFont
  246. {
  247.     float size = SIZE;
  248.     int fn;
  249.     fn = ((unsigned)random()) % numFonts;
  250.  
  251.     while([self isExcluded:fontNames[fn]]){
  252.         fn++;
  253.         if (!fontNames[fn]) fn=0;
  254.     }
  255.     fontNum = fn;
  256.     font = [Font newFont:fontNames[fn] size:size matrix:NX_FLIPPEDMATRIX];
  257.     return self;
  258. }
  259.  
  260. - drawSelf:(const NXRect *)r :(int)c
  261. {
  262.     if (!r || !c || c==2) return self;
  263.  
  264.     NXSetColor(NX_COLORBLACK);
  265.     NXRectFill(r);
  266.     [self doDrawing];
  267.     return self;
  268. }
  269.  
  270. - prepColor;
  271. {
  272.     if (useColor) {
  273.         blue = ((float)((unsigned)random()))/(float)INT_MAX;
  274.         red = ((float)((unsigned)random()))/(float)INT_MAX;
  275.         green = ((float)((unsigned)random()))/(float)INT_MAX;
  276.     } else {
  277.         blue = red = green = (((unsigned)random())%3) / 3.0 + (1.0/3.0);
  278.     }
  279.     currentColor = NXConvertRGBToColor(red,blue,green);
  280.     return self;
  281. }
  282.  
  283.  
  284. - prepPosition;
  285. {
  286.     xpos = (((unsigned)random()) % (unsigned)((maxCoord.x-MINX))) +MINX;
  287.     ypos = (((unsigned)random()) % (unsigned)((maxCoord.y-MINY))) +MINY;
  288.     return self;
  289. }
  290.  
  291. - prepWord;
  292. {
  293.     unsigned i = ((unsigned)random()) % (maxlen-1);
  294.     int j;
  295.     numWords = 0;
  296.     do {    
  297.         i--;
  298.         for(i=(i)?i:i-1;(words[i]!='\n' && i);i--);
  299.     } while(i && words[i-1]!='%');
  300.     for(j=0,i++;i<maxlen && !(words[i]=='\n' && words[i+1]=='%');i++,j++) {
  301.         if (j>=maxWordLen) {
  302.             if (!maxWordLen) maxWordLen = 256;
  303.             currentWord = realloc(currentWord,maxWordLen*2);
  304.             maxWordLen*=2;
  305.         }
  306.         if (fortuneFile == OFFENSIVE) {
  307.             currentWord[j]=ROT(words[i]);
  308.         } else {
  309.             currentWord[j]=words[i];
  310.         }
  311.         if (currentWord[j]==' ' || currentWord[j]=='\t'){
  312.             numWords++;
  313.         }
  314.     }
  315.     currentWord[j]='\0';
  316.     return self;
  317. }
  318.  
  319. - prep;
  320. {
  321.     [self prepFont];
  322.     [self prepColor];
  323.     [self prepPosition];
  324.     [self prepWord];
  325.     return self;
  326. }
  327.  
  328. - setGray:sender
  329. {
  330.     gray = [sender state];
  331.     [fe setDrawBacking:gray];
  332.     [self perform:@selector(writeBackingDefault:)
  333.         with:self
  334.         afterDelay:1000
  335.         cancelPrevious:YES];
  336.     [self lockFocus];
  337.     [self doDrawing];
  338.     [self unlockFocus];
  339.     return self;
  340. }
  341.  
  342. - useColor:sender;
  343. {
  344.     useColor = [sender state];
  345.     [self perform:@selector(writeColorDefault:) with:self afterDelay:1000 cancelPrevious:YES];
  346.     return self;
  347. }
  348.  
  349. - setHighlightSwitch:sender
  350. {
  351.     highlightSwitch = sender;
  352.     [highlightSwitch setState:gray];
  353.     return self;
  354. }
  355.  
  356. static void do_size(void)
  357. {
  358. #ifdef TEST1
  359.         system("ps -u | grep BackSpace.app | grep -v grep | awk ' { print \"Size:     V=\"$5\", R=\"$6 } '");
  360. #endif
  361. }
  362.        
  363. - setOffensiveMatrix:sender;
  364. {
  365.     offensiveMatrix = sender;
  366.     [offensiveMatrix selectCellWithTag:fortuneFile];
  367.     return self;
  368. }
  369.  
  370. - doDrawing;
  371. {
  372.     static const char * oldCurrent = 0;
  373.     NXRect dr = bounds;
  374.  
  375.     do_size();
  376.     [self prep];
  377.     [window disableFlushWindow];
  378.         [fe setStringValue:""];
  379.         [fe display];
  380.         dr = bounds;
  381.         dr.size.width-=10.0;
  382.         [fe setFrame:&dr];
  383.     [window disableDisplay];
  384.         [fe setTextColor:currentColor];
  385.         while(1){
  386.             int sz = [font pointSize];
  387.             if (sz<=2) {
  388.                 /*
  389.                  * problem in NeXT's stuff it seems. Too many
  390.                  * tabs in the TextFields will make the width
  391.                  * impossible to calculate. If this happens,
  392.                  * then we just try to do our best.
  393.                  */
  394.                 
  395.                 font = [Font newFont:fontNames[fontNum] size:8 matrix:[font matrix]];
  396.                 [fe setFrame:&bounds];
  397.                 [fe setFont:font];
  398. #ifdef TEST2
  399.                 printf("%s\n",currentWord);
  400. #endif
  401.                 break;
  402.             }
  403. #ifdef TESTING
  404.             [fe getFrame:&dr];
  405.             printf("Original font: (%s,%d) \t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
  406.                    [font familyName],sz,dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
  407. #endif
  408.             [fe setFont:font];
  409.             [fe setStringValueNoCopy:currentWord];
  410.             [fe sizeToFit];
  411.             [fe getFrame:&dr];
  412. #ifdef TESTING
  413.             printf("Sized to fit ================\t: {{%.0f,%.0f},{%.0f,%.0f}}\n",
  414.                    dr.origin.x,dr.origin.y,dr.size.width,dr.size.height);
  415. #endif
  416.             if (dr.size.width>(bounds.size.width-10.0)) {
  417.                 font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
  418.             } else if (dr.size.height>=bounds.size.height) {
  419.                 font = [Font newFont:fontNames[fontNum] size:sz-2 matrix:[font matrix]];
  420.             } else {
  421.                 break;
  422.             }
  423.         }
  424.     [window reenableDisplay];
  425.         [fe moveTo:(bounds.size.width-dr.size.width)/2.0
  426.               :(bounds.size.height-dr.size.height)/2.0];
  427.         [fe display];
  428.     [[window reenableFlushWindow] flushWindow];
  429.     oldCurrent = currentWord;
  430.     return self;
  431. }
  432.  
  433. - oneStep
  434. {
  435.     static unsigned lastTime = 0;
  436.     unsigned thisTime = currentTimeInMs();
  437.     
  438.     if (thisTime-lastTime<(threshold*numWords)) return self;
  439.     lastTime = thisTime;
  440.     if (!appowner) return self;
  441.     [window makeFirstResponder:fe];
  442.     [self doDrawing];
  443.     return self;
  444. }
  445.  
  446.  
  447. - (BOOL)useBufferedWindow
  448. {
  449.     return YES;
  450. }
  451.  
  452. - allocTextObject;
  453. {
  454.     id tf = [[ThreeField alloc] initFrame:&frame];
  455.     [tf allocateGState];
  456.     [tf setClipping:NO];
  457.     [tf setBezeled:NO];
  458.     [tf setBordered:NO];
  459. //    [tf setBackgroundGray:NX_BLACK];
  460.     [tf setBackgroundTransparent:YES];
  461.     [tf setAutodisplay:NO];
  462.     [tf setEditable:NO];
  463.     [tf setSelectable:YES];
  464.     [self addSubview:tf];
  465.     return tf;
  466. }
  467.  
  468.  
  469. - setup;
  470. {
  471.     char buf[MAXPATHLEN];
  472.     appowner = BSThinker();
  473.     sprintf(buf,"%s/FortuneView.nib",[appowner moduleDirectory:"Fortune"]);
  474.     [NXApp loadNibFile:buf owner:self withNames:NO];
  475.     [self loadWords:fortunes[fortuneFile]];
  476.     [speedSlider setIntValue:threshold];
  477.     [highlightSwitch setState:gray];
  478.     fe = [self allocTextObject];
  479.     [fe setDrawBacking:gray];
  480.     [colorSwitch setState:useColor];
  481.     [availableBrowser setCellClass:[GrayCell class]];
  482.     return self;
  483. }
  484.  
  485. #define STRDUP(a) ((a)?(strcpy(malloc((strlen(a)+1)*sizeof(char)),a)):0)
  486.  
  487. - writeExcludedDefault:sender
  488. {
  489.     int i,length,count = numFonts;
  490.     char * def;
  491.     char * out;
  492.     for(length=i=0;i<count;i++){
  493.         if (excluded[i] == EXCLUDED){
  494.             length += (strlen(fontNames[i])+4);
  495.         }
  496.     }
  497.     def = malloc((length+1) * sizeof(char));
  498.     *def='\0';
  499.     for(out=def,length=i=0;i<count;i++){
  500.         if (excluded[i] == EXCLUDED){
  501.             const char * current = fontNames[i];
  502.             length = strlen(current);
  503.             strcat(out,current);
  504.             if (i<(count-1)) strcat(out,"; ");
  505.             out+=length+2;
  506.         }
  507.     }
  508.     NXWriteDefault([NXApp appName],EXFONTS,def);
  509.     free(def);
  510.     return self;
  511. }
  512.  
  513. - showFonts:sender;
  514. {
  515.     [fontWindow makeKeyAndOrderFront:self];
  516.     return self;
  517. }
  518.  
  519. - (int)browser:sender fillMatrix:matrix inColumn:(int)col;
  520. {
  521.     int i;
  522.     [matrix renewRows:0 cols:0];
  523.     /*
  524.      * I've had some trouble doing this through the browser.
  525.      */
  526.     [[matrix setPrototype:[[GrayCell alloc] init]] free];
  527.     for(i=0; fontNames[i]; i++){
  528.         id cell;
  529.         [matrix addRow];
  530.         cell = [matrix cellAt:i:0];
  531.         [cell setStringValueNoCopy:fontNames[i]];
  532.         [cell setGrayed:[self isExcluded:fontNames[i]]];
  533.         [cell setLoaded:YES];
  534.         [cell setLeaf:YES];
  535.     }
  536.     return i;
  537. }
  538.  
  539. - setAvailableBrowser:sender;
  540. {
  541.     availableBrowser = sender;
  542.     [sender setTarget:self];
  543.     [sender setDoubleAction:@selector(toggleFont:)];
  544.     return self;
  545. }
  546.  
  547. - toggleFont:sender;
  548. {
  549.     id matrix = [availableBrowser matrixInColumn:0];
  550.     id cell = [matrix cellAt:[matrix selectedRow]:0];
  551.     if ([self isExcluded:[cell stringValue]]){
  552.         [self removeFont:self];
  553.     } else {
  554.         [self addFont:self];
  555.     }
  556.     return self;
  557. }
  558.  
  559. /*
  560.  * ADD to the excluded list!
  561.  */
  562. - addFont:sender;
  563. {
  564.     id matrix = [availableBrowser matrixInColumn:0];
  565.     id cell = [matrix cellAt:[matrix selectedRow]:0];
  566.  
  567.     int i,count;
  568.     for(count=i=0;i<numFonts;i++) count+=(excluded[i]==INCLUDED);
  569.     if (count==1) {
  570.         NXRunAlertPanel("Hey!", "You have to keep at least one font around!",0,0,0);
  571.     } else {
  572.         excluded[[matrix selectedRow]]=EXCLUDED;
  573.         [[availableBrowser window] disableFlushWindow];
  574.         [cell setGrayed:YES];
  575.         [availableBrowser display];
  576.         [[[availableBrowser window] reenableFlushWindow] flushWindow];
  577.         [self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
  578.     }
  579.     return self;
  580. }
  581.  
  582. /*
  583.  * REMOVE from the excluded list!
  584.  */
  585. - removeFont:sender;
  586. {
  587.     int col = [availableBrowser selectedColumn];
  588.     if (col==0) {
  589.         id matrix = [availableBrowser matrixInColumn:col];
  590.         int row = [matrix  selectedRow]; 
  591.         if (row>=0){
  592.             excluded[row]=INCLUDED;
  593.             [self perform:@selector(writeExcludedDefault:) with:self afterDelay:1000 cancelPrevious:YES];
  594.             [[availableBrowser window] disableFlushWindow];
  595.             [[matrix cellAt:row:0] setGrayed:NO];
  596.             [availableBrowser display];
  597.             [[[availableBrowser window] reenableFlushWindow] flushWindow];
  598.         }
  599.     }
  600.     return self;
  601. }
  602.  
  603. - excludeFont:(const char *)p;
  604. {
  605.     int i;
  606.     for(i=0;fontNames[i];i++){
  607.         if (strcmp(p,fontNames[i])==0){
  608.             excluded[i]=EXCLUDED;
  609.             break;
  610.         }
  611.     }
  612.     return self;
  613. }
  614.         
  615. - setExcludedFonts:(const char *)list;
  616. {
  617.     char array[strlen(list)+1];
  618.     char * p;
  619.     int i;
  620.  
  621.     excluded = (char *)malloc(sizeof(char)*numFonts);
  622.     for(i=0;i<numFonts;i++) excluded[i]=INCLUDED;
  623.     strcpy(array,list);
  624.     for(i=0,p=array;;i++){
  625.         if (array[i]==';' || array[i]=='\0') {
  626.             int j;
  627.             BOOL shouldBreak = (array[i]=='\0');
  628.             array[i]='\0';
  629.             for(j=i-1;(array[j]=='\t' || array[j]==' ');j--) array[j]='\0';
  630.             [self excludeFont:p];
  631.             if (shouldBreak) break;
  632.             p=array+i+1;
  633.             while(*p==' ' || *p=='\t') p++;
  634.         }
  635.     }
  636.     return self;
  637. }
  638.             
  639. - initFrame:(const NXRect *)frameRect
  640. {
  641.     const char *t;
  642.     [super initFrame:frameRect];
  643.     [self allocateGState];        // For faster lock/unlockFocus
  644.     [self setClipping:NO];// even faster...
  645.  
  646.     [self loadFonts];
  647.     [self newViewSize];
  648.     srandom(time(0));
  649.     if (t = NXReadDefault([NXApp appName],SPEED)){
  650.         threshold = atoi(t);
  651.     } else {
  652.         threshold = 200;
  653.     }
  654.     if (t= NXReadDefault([NXApp appName],BACKING)){
  655.         gray = atoi(t);
  656.     } else {
  657.         gray = PLAIN;
  658.     }
  659.     if (t= NXReadDefault([NXApp appName],USECOLOR)){
  660.         useColor = (strcmp(t,"YES")==0);
  661.     } else {
  662.         useColor = YES;
  663.     }
  664.     if (t= NXReadDefault([NXApp appName],F_FILE)){
  665.         fortuneFile = (atoi(t));
  666.     } else {
  667.         fortuneFile = NORMAL;
  668.     }
  669.     if (t = NXReadDefault([NXApp appName],EXFONTS)){
  670.         [self setExcludedFonts:t];
  671.     } else {
  672.         [self setExcludedFonts:"Symbol ; Lexi"];
  673.     }
  674.     [self setup];
  675.     return self;
  676. }
  677.  
  678. - sizeTo:(NXCoord)width :(NXCoord)height
  679. {
  680.     [super sizeTo:width :height];
  681.     [self newViewSize];
  682.     return self;
  683. }
  684.  
  685. - newViewSize
  686. {
  687.     if (oldSize.width == bounds.size.width &&
  688.             oldSize.height == bounds.size.height)
  689.     {
  690.         return self;
  691.     }else{
  692.         oldSize.width = bounds.size.width;
  693.         oldSize.height = bounds.size.height;
  694.     }
  695.     maxCoord.x = bounds.size.width-150.0;
  696.     maxCoord.y = bounds.size.height;
  697.     if (maxCoord.x < 0) maxCoord.x = 0;
  698.     if (maxCoord.y < 0) maxCoord.y = 0;
  699.     [fe setFrame:&frame];
  700.     if ([self window]){
  701.         [self display];
  702.     }
  703.     return self;
  704. }
  705.  
  706. - (const char *)windowTitle
  707. {
  708.     return "FortuneView";
  709. }
  710.  
  711. - inspector:sender
  712. {
  713.     appowner=sender;
  714.     if (!sharedInspectorPanel){
  715.         [self setup];
  716.     }
  717.     return sharedInspectorPanel;
  718. }
  719.  
  720. @end
  721.  
  722.